const isBool = t => typeof t === 'boolean';
const isStr = t => typeof t === 'string';

function isLoadingEventValid(value) {
  if (isBool(value)) return true;
  if (Array.isArray(value)) {
    return value.every(isStr);
  }
  if (isStr(value)) return true;
  return false;
}

function getWrapEventKeys(evt, listeners) {
  if (isBool(evt)) {
    return evt ? Object.keys(listeners) : [];
  }
  if (isStr(evt)) {
    return Object.prototype.hasOwnProperty.call(listeners, evt) ? [evt] : [];
  }
  if (Array.isArray(evt)) {
    return Object.keys(listeners).filter(k => evt.includes(k));
  }
  return [];
}

export default {
  props: {
    loadingEvent: {
      type: [Boolean, Array, String],
      default: 'click',
      validator: isLoadingEventValid
    },
    loadingType: {
      type: String,
      default: 'loading',
      validator(value) {
        return ['loading', 'disabled'].some(type => type === value);
      }
    },
    closeLoadingManually: {
      type: Boolean,
      default: false
    }
  },

  data() {
    return {
      loadingQueue: []
    };
  },

  computed: {
    listeners() {
      const keys = getWrapEventKeys(this.loadingEvent, this.$listeners);
      const wrap = k => this.wrapListener(k, this.$listeners[k]);
      return {
        ...this.$listeners,
        ...keys.reduce((o, k) => ({ ...o, [k]: wrap(k) }), {})
      };
    },

    loading() {
      return !!this.loadingQueue.length;
    },

    attrs() {
      return { ...this.$attrs, [this.loadingType]: this.loading };
    }
  },

  methods: {
    wrapListener(key, handler) {
      async function loadingEventHandler(...rest) {
        const [e] = rest;
        if (e instanceof Event) {
          e.preventDefault();
          e.stopPropagation();
        }
        this.loadingQueue = [...this.loadingQueue, key];
        try {
          return await new Promise((resolve, reject) => {
            let resolved = false;
            const makeCb =
              fn =>
              (...args) => {
                if (resolved) return;
                fn(...args);
                resolved = true;
              };
            const resolveCb = makeCb(resolve);
            const rejectCb = makeCb(reject);
            const res = handler(...rest, resolveCb);

            // close loading automatically
            if (!this.closeLoadingManually) {
              // Promise
              if (res instanceof Promise) {
                res.then(resolveCb).catch(rejectCb);
                // Non-promise
              } else {
                resolveCb();
              }
            }

            // Want to close loading manually;
          });
        } finally {
          this.loadingQueue = this.loadingQueue.filter(k => k !== key);
        }
      }
      return loadingEventHandler.bind(this);
    }
  }
};
